package com.security;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.common.Constants;
import com.common.Status;
import com.config.JwtConfig;
import com.pojo.entity.SysPermission;
import com.pojo.entity.SysRole;
import com.pojo.entity.SysUser;
import com.pojo.vo.TokenVo;
import com.pojo.vo.UserVo;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * Token服务类
 * </p>
 */
@EnableConfigurationProperties(JwtConfig.class)
@Configuration
@Slf4j
public class TokenService {
    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    // 生成全局的key
    Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    @Autowired
    CustomSigningKeyResolver customSigningKeyResolver;

    /**
     * 创建Token
     *
     * @param rememberMe     记住我
     * @param id             用户id
     * @param subject        用户名
     * @param jsonRole       json格式用户角色列表
     * @param jsonPermission json格式用户权限列表
     * @return token
     */
    public TokenVo createToken(Boolean rememberMe, Long id,
                               String subject,
                               String jsonRole,
                               String jsonPermission) {
        Date now = new Date();
        //SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getKey().getBytes(StandardCharsets.UTF_8));
        Key keyUser = customSigningKeyResolver.lookupVerificationKey(String.valueOf(id));
        JwtBuilder builder = Jwts.builder()
                .setId(id.toString())
                .setHeaderParam(JwsHeader.KEY_ID, String.valueOf(id))
                .setSubject(subject).setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, keyUser)
                .claim("roles", jsonRole).claim("authorities", jsonPermission);
        // 设置过期时间
        Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
        if (ttl > 0) {
            builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue()));
        }
        String token = builder.compact();
        // 将生成的token保存至Redis
        stringRedisTemplate.opsForValue().set(Constants.REDIS_JWT_KEY_PREFIX + subject, token, ttl, TimeUnit.MILLISECONDS);
        return new TokenVo(token, now.getTime(), ttl / 1000);
    }

    /**
     * 创建Token
     *
     * @param authentication 用户认证信息
     * @param rememberMe     记住我
     * @return Token
     */
    public TokenVo createToken(Authentication authentication, Boolean rememberMe) {
        UserVo userVo = (UserVo) authentication.getPrincipal();
        return createToken(rememberMe, userVo.getUser().getId(), userVo.getUsername(),
                JSON.toJSONString(userVo.getRoleList()), JSON.toJSONString(userVo.getPermissionList()));
    }

    /**
     * 解析token
     *
     * @param token Token
     * @return {@link Claims}
     */
    public Claims parseToken(String token) {
        try {
            //SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getKey().getBytes(StandardCharsets.UTF_8));
            //Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
            Claims claims = Jwts.parser()
                    .setSigningKeyResolver(customSigningKeyResolver) // 为每个用户使用单独的SecretKey
                    //.setSigningKey(key)
                    .parseClaimsJws(token).getBody();
            String username = claims.getSubject();
            String redisKey = Constants.REDIS_JWT_KEY_PREFIX + username;

            // 校验redis中的Token是否存在
            Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
            if (Objects.isNull(expire) || expire <= 0) {
                throw new SecurityException(Status.TOKEN_EXPIRED.toString());
            }

            // 校验redis中的Token是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表Token已过期
            String redisToken = stringRedisTemplate.opsForValue().get(redisKey);
            if (!StrUtil.equals(token, redisToken)) {
                throw new SecurityException(Status.TOKEN_OUT_OF_CTRL.toString());
            }
            return claims;
        } catch (ExpiredJwtException e) {
            log.error("Token 已过期");
            throw new SecurityException(Status.TOKEN_EXPIRED.toString());
        } catch (UnsupportedJwtException e) {
            log.error("不支持的 Token");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR.toString());
        } catch (MalformedJwtException e) {
            log.error("Token 无效");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR.toString());
        } catch (SignatureException e) {
            log.error("无效的 Token 签名");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR.toString());
        } catch (IllegalArgumentException e) {
            log.error("Token 参数不存在");
            throw new SecurityException(Status.TOKEN_PARSE_ERROR.toString());
        }
    }

    /**
     * 设置JWT过期
     *
     * @param request 请求
     */
    public void invalidateToken(HttpServletRequest request) {
        String token = getTokenFromRequest(request);
        String username = getUsernameFromToken(token);
        // 从redis中清除JWT
        stringRedisTemplate.delete(Constants.REDIS_JWT_KEY_PREFIX + username);
    }

    /**
     * 根据 token 获取用户名
     *
     * @param token Token
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }

    public UserVo getUserFromToken(String token) {
        Claims claims = parseToken(token);
        String username = claims.getSubject();
        SysUser user = new SysUser();
        user.setUsername(username);
        String roles = claims.get("roles", String.class);
        String authorities = claims.get("authorities", String.class);
        List<SysPermission> permissionList = JSON.parseArray(authorities, SysPermission.class);
        List<SysRole> roleList = JSON.parseArray(roles, SysRole.class);
        UserVo userVo = new UserVo(user, roleList, permissionList);
        return userVo;
    }

    /**
     * 从 request 的 header 中获取 token
     *
     * @param request 请求
     * @return token
     */
    public String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StrUtil.isNotBlank(bearerToken)) {
            return bearerToken;
        }
        return null;
    }

}
